﻿/*
 * Created by SharpDevelop.
 * User: issuser
 * Date: 2011-1-20
 * Time: 10:46
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Linq;
using System.Reflection;
using NLite.Reflection.Internal;
using NLite.Collections;
using System.Collections.Generic;
using NLite.Log;
using System.IO;

namespace NLite.Reflection
{
    /// <summary>
    /// Description of IAssemblyLoader.
    /// </summary>
    public interface IAssemblyLoader
    {
        /// <summary>
        /// 得到所有已经加载的Assembly
        /// </summary>
        /// <returns></returns>
        Assembly[] GetAssemblies();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        Assembly Load(string assemblyName);

        /// <summary>
        /// 加载Assembly
        /// </summary>
        /// <param name="assemblyFile">assembly 文件</param>
        /// <returns>返回加载成功后的Assembly</returns>
        Assembly LoadFromFile(string assemblyFile);

        /// <summary>
        /// 加载目录下的所有Assembly（不包含子目录）
        /// </summary>
        /// <param name="directory">目录</param>
        void LoadFromDirectory(string directory);

        /// <summary>
        /// 加载目录下的所有Assembly
        /// </summary>
        /// <param name="directory">目录</param>
        /// <param name="includeChildDirectory">是否包含子目录</param>
        void LoadFromDirectory(string directory, bool includeChildDirectory);

        /// <summary>
        /// 加载目录下的所有Assembly
        /// </summary>
        /// <param name="directory">目录</param>
        /// <param name="filter">过滤条件</param>
        /// <param name="includeChildDirectory">是否包含子目录</param>
        void LoadFromDirectory(string directory, string filter, bool includeChildDirectory);
    }


    /// <summary>
    /// 
    /// </summary>
    public class AssemblyLoader
    {
        private static DefaultAssemblyLoader instance;

        private static void Check()
        {
            if (instance == null)
                instance = new DefaultAssemblyLoader();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="assembylyName"></param>
        /// <returns></returns>
        public static Assembly Load(string assembylyName)
        {
            Check();
            return instance.Load(assembylyName);
        }

        /// <summary>
        /// 加载Assembly
        /// </summary>
        /// <param name="assemblyFile">assembly 文件</param>
        /// <returns>返回加载成功后的Assembly</returns>
        public static Assembly LoadFromFile(string assemblyFile)
        {
            Check();
            return instance.LoadFromFile(assemblyFile);
        }

        /// <summary>
        /// 加载目录下的所有Assembly（不包含子目录）
        /// </summary>
        /// <param name="directory">目录</param>
        public static void LoadFromDirectory(string directory)
        {
            Check();
            instance.LoadFromDirectory(directory);
        }

        /// <summary>
        /// 加载目录下的所有Assembly
        /// </summary>
        /// <param name="directory">目录</param>
        /// <param name="includeChildDirectory">是否包含子目录</param>
        public static void LoadFromDirectory(string directory, bool includeChildDirectory)
        {
            Check();
            instance.LoadFromDirectory(directory, includeChildDirectory);
        }

        /// <summary>
        /// 加载目录下的所有Assembly
        /// </summary>
        /// <param name="directory">目录</param>
        /// <param name="filter">过滤条件</param>
        /// <param name="includeChildDirectory">是否包含子目录</param>
        public static void LoadFromDirectory(string directory, string filter, bool includeChildDirectory)
        {
            Check();
            instance.LoadFromDirectory(directory, filter, includeChildDirectory);
        }

    }

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public abstract class AbstractAssemblyLoader : IAssemblyLoader
    {

        private List<Assembly> cache = new List<Assembly>();
        

        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        public abstract Assembly Load(string assemblyName);


        /// <summary>
        /// 得到所有已经加载的Assembly
        /// </summary>
        /// <returns></returns>
        public Assembly[] GetAssemblies()
        {
            return cache.ToArray();
        }

        /// <summary>
        /// 加载Assembly
        /// </summary>
        /// <param name="assemblyFile">assembly 文件</param>
        /// <returns>返回加载成功后的Assembly</returns>
        public Assembly LoadFromFile(string assemblyFile)
        {
            try
            {
                var asm = Assembly.LoadFrom(assemblyFile);
                if (asm != null)
                {
                    if (!cache.Contains(asm))
                        cache.Add(asm);

                    var dependancies = asm.GetReferencedAssemblies();
                    foreach (var item in dependancies)
                    {
                        var depAsm = Assembly.Load(item);
                        if (!cache.Contains(depAsm))
                            cache.Add(depAsm);
                    }
                }
                return asm;
            }
            catch (Exception ex)
            {
                ex.Handle();
                return null;
            }
        }

        /// <summary>
        /// 加载目录下的所有Assembly（不包含子目录）
        /// </summary>
        /// <param name="directory">目录</param>
        public void LoadFromDirectory(string directory)
        {
            LoadFromDirectory(directory, "*.dll", false);
        }

        /// <summary>
        /// 加载目录下的所有Assembly
        /// </summary>
        /// <param name="directory">目录</param>
        /// <param name="includeChildDirectory">是否包含子目录</param>
        public void LoadFromDirectory(string directory, bool includeChildDirectory)
        {
            LoadFromDirectory(directory, "*.dll", includeChildDirectory);
        }

        /// <summary>
        /// 加载目录下的所有Assembly
        /// </summary>
        /// <param name="directory">目录</param>
        /// <param name="filter">过滤条件</param>
        /// <param name="includeChildDirectory">是否包含子目录</param>
        public void LoadFromDirectory(string directory, string filter, bool includeChildDirectory)
        {
            if (string.IsNullOrEmpty(filter))
                filter = "*.dll";
            else if (!filter.EndsWith("*.dll"))
                throw new ArgumentException("invalid filter. the filter format should like be *.dll.");
            if (Directory.Exists(directory))
            {
                var files = Directory.GetFiles(directory, filter);
                if (files != null && files.Length > 0)
                    foreach (string file in files)
                        LoadFromFile(file);

                if (!includeChildDirectory) return;

                var dirs = Directory.GetDirectories(directory);
                if (dirs != null && dirs.Length > 0)
                    foreach (var item in dirs)
                        LoadFromDirectory(item, filter, includeChildDirectory);
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class DefaultAssemblyLoader : AbstractAssemblyLoader
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(DefaultAssemblyLoader));

        private readonly IEnumerable<IAssemblyNameResolver> _assemblyNameResolvers;
        private readonly Map<string, Assembly> _loadedAssemblies = new Map<string, Assembly>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// 
        /// </summary>
        public DefaultAssemblyLoader()
            : this(new IAssemblyNameResolver[] { new ApplicationPathAssemblyNameResolver(),new GacAssemblyNameResolver()})
        {
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyNameResolvers"></param>
        public DefaultAssemblyLoader(IEnumerable<IAssemblyNameResolver> assemblyNameResolvers)
        {
            _assemblyNameResolvers = assemblyNameResolvers;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        public override Assembly Load(string assemblyName)
        {
            try
            {
                var shortName = this.ExtractAssemblyShortName(assemblyName);
                return _loadedAssemblies.GetOrAdd(shortName, () => OnLoad(shortName));
            }
            catch (Exception e)
            {
                log.Error(string.Format("Error loading assembly '{0}'", assemblyName), e);
                return null;
            }
        }

        private Assembly OnLoad(string shortName)
        {
            Assembly result = AppDomain
                .CurrentDomain
                .GetAssemblies()
                .FirstOrDefault(item => string.Equals(shortName, item.GetName().Name, StringComparison.OrdinalIgnoreCase));

            if (result != null)
                return result;

            // Try resolving the short name to a full name
            var resolvedName = _assemblyNameResolvers
                .Select(r => r.Resolve(shortName))
                .Where(f => !string.IsNullOrEmpty(f))
                .FirstOrDefault();

            if (resolvedName != null)
            {
                return Assembly.Load(resolvedName);
            }

            return null;
        }

       
    }

    /// <summary>
    /// 
    /// </summary>
    public interface IAssemblyNameResolver
    {
        /// <summary>
        /// Resolve a short assembly name to a full name
        /// </summary>
        string Resolve(string shortName);
    }

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class AppDomainAssemblyNameResolver : IAssemblyNameResolver
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="shortName"></param>
        /// <returns></returns>
        public string Resolve(string shortName)
        {
            return AppDomain.CurrentDomain
                .GetAssemblies()
                .Where(a => StringComparer.OrdinalIgnoreCase.Equals(shortName, AssemblyLoaderExtensions.ExtractAssemblyShortName(a.FullName)))
                .Select(a => a.FullName)
                .SingleOrDefault();
        }
    }

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class GacAssemblyNameResolver : IAssemblyNameResolver
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="shortName"></param>
        /// <returns></returns>
        public string Resolve(string shortName)
        {
            return GacCache.GetAssemblyFullName(shortName);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    [Serializable]
    public class ApplicationPathAssemblyNameResolver : IAssemblyNameResolver
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="shortName"></param>
        /// <returns></returns>
        public string Resolve(string shortName)
        {
            var assemblyFile = NLiteEnvironment.ApplicationPhysicalPath + shortName + ".dll";
            if (File.Exists(assemblyFile))
            {
                var asm = Assembly.LoadFile(assemblyFile);
                return asm.FullName;
            }
            return null;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public static class AssemblyLoaderExtensions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyLoader"></param>
        /// <param name="fullName"></param>
        /// <returns></returns>
        public static string ExtractAssemblyShortName(this IAssemblyLoader assemblyLoader, string fullName)
        {
            return ExtractAssemblyShortName(fullName);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fullName"></param>
        /// <returns></returns>
        public static string ExtractAssemblyShortName(string fullName)
        {
            int index = fullName.IndexOf(',');
            if (index < 0)
                return fullName;
            return fullName.Substring(0, index);
        }
    }
}
